// ---------------------------------------------------------------------------
//  File:user_interface.cpp
//
//  Description:
//
//  Author: golden.colorado.5280@gmail.com
//
//  Date: December 26th, 2015
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
//  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//  DEALINGS IN THE SOFTWARE.
// ---------------------------------------------------------------------------

#include "user_interface.h"
#include "color_support.h"
#include "ring_builder.h"

//---------------------Type Conversion----------------------------------------------------------
template <typename T> std::string to_string(T const& value) {
    std::stringstream sstr;
    sstr << std::setprecision(6) << std::fixed << value;
    return sstr.str();
}

template <typename T> char* to_char(T const& value, unsigned int precision) {
    std::stringstream sstr;
    sstr << std::setprecision(precision) << std::fixed << value;

    std::string s = sstr.str();
    char *fchar = new char[s.size()+1];
    strncpy(fchar, s.c_str(), s.size()+1);

    return fchar;
}

//-------------------------Type Definitions---------------------------------------------------------
class DataField : public Fl_Input{
public:

    DataField(int x, int y, int w, int h, const char* title=0) : Fl_Input(x,y,w,h,title){
        box = new Fl_Box (x+w, y, w, h, "");
        box->box(FL_UP_BOX);
        box->labelsize (FONTSIZE);
        precision = 6;
    }

    void initialize(double d){
        data = d;
        value("");  //clear the input box so the user can add a new number
        box->label( to_char(d, precision) );
    }

    void setMinMax(double minimum, double maximum){
        min_limit = minimum;
        max_limit = maximum;
    }

    void setPrecision(unsigned int p){
        precision = p;
    }

    void updateValue(){
        double d = atof(value());

        if ( (d >= min_limit) && (d <= max_limit) ){
            initialize(d); //accept the form field and update data
        }
        initialize(data);  //reset the form filed to the original value
    }

    double getData(){
        return data;
    }

private:
    Fl_Box *box;
    double data;
    double max_limit;
    double min_limit;
    unsigned int precision;
};


class TextField : public Fl_Input{
public:
    TextField(int x, int y, int w, int h, const char* title=0) : Fl_Input(x,y,w,h,title){
        box = new Fl_Box (x+w, y, w, h, "");
        box->box(FL_UP_BOX);
        box->labelsize (FONTSIZE);
    }

    void initialize(std::string s){
        data = s;
        value("");  //clear the input box so the user can add a new number
        box->label( data.c_str() );
    }

    void updateValue(){
        std::string s = value();

        //Before updating the field make sure the input string was valid
        std::string::iterator it;
        int index = 0;
        for ( it = s.begin() ; it < s.end(); it++ ,index++){
            //Replace non-alphanumeric characters with underscores
            if(!isalnum ( *it ) ){
                s.replace(index,1,"_");
            }
        }
        initialize(s);
    }

    std::string getData(){
        return data;
    }

private:
    Fl_Box *box;
    std::string data;
};





class ColorSelectField : public Fl_Button{
public:

    ColorSelectField(int x, int y, int w, int h, const char* title=0) : Fl_Button(x,y,w,h,title){
        box = new Fl_Box (x+w, y, 20, h);
        box->box(FL_UP_BOX);
        color_index = FL_GRAY;
        box->color(color_index);
    }

    void setColor(int c){
        color_index = c;
        box->color(color_index);
        box->redraw();
    }

    RGB getColorRGB(){
        //RGB values are packed in a 32-bit unsigned integer with
        //the red value in the upper 8 bits, the green value
        //in the next 8 bits, and the blue value in bits 8-15.
        //The lower 8 bits will always be 0.
        unsigned int c_packed_binary =  Fl::get_color(color_index);
        RGB c;
        c.r   = ( (unsigned char)(c_packed_binary>>24) ) / 256.f;
        c.g   = ( (unsigned char)(c_packed_binary>>16) ) / 256.f;
        c.b   = ( (unsigned char)(c_packed_binary>>8)  ) / 256.f;
        return c;
    }

private:
    Fl_Color color_index;
    Fl_Box *box;
};


typedef struct {
   DataField *latitude;
   DataField *longitude;
   DataField *radius;
   DataField *stepSize;
   DataField *totalRings;
   DataField *opacity;
   DataField *thickness;
   ColorSelectField *innerColor;
   ColorSelectField *outerColor;
   ColorSelectField *outlineColor;
   TextField *kmlName;
} RADIUS_RING_T;

//-------------------- GUI Event Handlers -----------------------------------
static void Data_CB_Input(Fl_Input*, void* d) {
    DataField *field = (DataField*)d;
    field->updateValue();
}

//--CallBack to set Text Field
static void Text_CB_Input(Fl_Input*, void* d) {
    TextField *field = (TextField*)d;
    field->updateValue();
}

//--CallBack to set colors
void Button_CB_Color(Fl_Widget* buttonptr, void* d){

    ColorSelectField *c = (ColorSelectField*) d;
    c->setColor( fl_show_colormap(0) );
}

//--Menu Bar Callbacks
void Menu_CB_Info(Fl_Widget* w,void*){
    fl_message("Builds a series of concentric rings, each of a larger diameter, \nthat radiate out of the same point.");
}

void Menu_CB_Quit(Fl_Widget* w,void*){
    exit(0);
}

void Menu_CB_Build(Fl_Widget* buttonptr, void* d){

    RADIUS_RING_T *rr =  (RADIUS_RING_T*) d;

    multi_ring(rr->latitude->getData(),
               rr->longitude->getData(),
               rr->radius->getData(),
               rr->stepSize->getData(),
               rr->totalRings->getData(),
               rr->opacity->getData(),
               rr->innerColor->getColorRGB(),
               rr->outerColor->getColorRGB(),
               rr->outlineColor->getColorRGB(),
               rr->thickness->getData(),
               rr->kmlName->getData()
               );

    std::string parameters = rr->kmlName->getData() + ".kml created \n" +
                             "Centered at Latitude, Longitude(" +
                             to_string( rr->latitude->getData() ) +
                             "," + to_string( rr->longitude->getData() ) + ")";

    fl_alert(parameters.c_str());
}

//-Build the user interface
int user_interface(){
  Fl_Window *window;
  Fl_Menu_Bar *menubar;

  window = new Fl_Window (450, 300,"GIS Radial Buffer KML Tool");

  window->begin();

    //----Build Data Fields
    RADIUS_RING_T datafields;

    menubar = new Fl_Menu_Bar(0, 0, window->w(), 22);
    menubar->add("&Info", 0, Menu_CB_Info);
    menubar->add("&Build", 0, Menu_CB_Build, &datafields);
    menubar->add("&Quit", 0, Menu_CB_Quit);

    DataField* latitude = new DataField(220, 30, 100, 20,"Latitude (decimal degrees):");
    latitude->initialize( DEFAULT_LAT );
    latitude->setMinMax(-90.0,90.0);
    latitude->callback((Fl_Callback*)Data_CB_Input,latitude);
    latitude->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY);
    datafields.latitude = latitude;

    DataField* longitude = new DataField(220, 60, 100, 20,"Longitude (decimal degrees):");
    longitude->initialize( DEFAULT_LON );
    longitude->setMinMax(-180.0,180.0);
    longitude->callback((Fl_Callback*)Data_CB_Input,longitude);
    longitude->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY);
    datafields.longitude = longitude;

    DataField* radius = new DataField(220, 90, 100, 20,"Radius (km):");
    radius->setPrecision(1);
    radius->initialize( DEFAULT_RADIUS );
    radius->setMinMax(0,1000);
    radius->callback((Fl_Callback*)Data_CB_Input,radius);
    radius->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY);
    datafields.radius = radius;

    DataField* stepSize = new DataField(220, 120, 100, 20,"Step Size (km):");
    stepSize->setPrecision(1);
    stepSize->initialize( DEFAULT_STEP_SIZE );
    stepSize->setMinMax(0,1000);
    stepSize->callback((Fl_Callback*)Data_CB_Input,stepSize);
    stepSize->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY);
    datafields.stepSize = stepSize;

    DataField* totalRings = new DataField(220, 150, 100, 20,"Total Rings (1-100):");
    totalRings->setPrecision(0);
    totalRings->initialize( DEFAULT_NUM_RINGS );
    totalRings->setMinMax( 1, DEFAULT_MAX_RINGS );
    totalRings->callback((Fl_Callback*)Data_CB_Input,totalRings);
    totalRings->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY);
    datafields.totalRings = totalRings;

    DataField* opacity = new DataField(220, 180, 100, 20,"Opacity (0-100%):");
    opacity->setPrecision(0);
    opacity->initialize( DEFAULT_OPACITY );
    opacity->setMinMax( 0, 100 );
    opacity->callback((Fl_Callback*)Data_CB_Input,opacity);
    opacity->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY);
    datafields.opacity = opacity;

    DataField* lineThickness = new DataField(220, 210, 100, 20,"Line Thickness (0-10):");
    lineThickness->setPrecision(0);
    lineThickness->initialize( DEFAULT_LINE_THICKNESS );
    lineThickness->setMinMax( 0, 10 );
    lineThickness->callback((Fl_Callback*)Data_CB_Input,lineThickness);
    lineThickness->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY);
    datafields.thickness = lineThickness;

    TextField* kmlName = new TextField(180, 240, 120, 20,"KML Name:");
    kmlName->initialize( DEFAULT_KML_NAME ); //this is the default name of the kml file
    kmlName->callback((Fl_Callback*)Text_CB_Input,kmlName);
    kmlName->when(FL_WHEN_RELEASE | FL_WHEN_ENTER_KEY);
    datafields.kmlName = kmlName;

    //--------------Color Buttons--------------------------------------
    ColorSelectField *button_color_inner = new ColorSelectField(40,270,100,20,"Inner Color");
    button_color_inner->setColor(FL_YELLOW);
    button_color_inner->callback(Button_CB_Color, button_color_inner);
    datafields.innerColor = button_color_inner;

    ColorSelectField *button_color_outer = new ColorSelectField(170,270,100,20,"Outer Color");
    button_color_outer->setColor(FL_BLUE);
    button_color_outer->callback(Button_CB_Color, button_color_outer);
    datafields.outerColor = button_color_outer;

    ColorSelectField *button_color_outline = new ColorSelectField(300,270,100,20,"Outline Color");
    button_color_outline->setColor(FL_WHITE);
    button_color_outline->callback(Button_CB_Color, button_color_outline);
    datafields.outlineColor = button_color_outline;

  window->end ();
  window->show();

  return(Fl::run());
}
